<?PHP
/*
 * $Id: barge-http.classes.php 67 2009-05-30 03:41:47Z namba@craid.co.jp $
 * =============================================================================
 * Barge - Web application framework on PHP5
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 * =============================================================================
 * For more information, see http://barge.googlecode.com/
 */
namespace barge\http;

use barge\io\IOException;
use barge\utils\HashMap;
use barge\utils\ArrayList;

/**
 * @version 1.0 2009/03/20
 * @since Barge 1.2.1 2009/03/20
 */
class HttpClientException extends IOException {}

/**
 * @version 1.0 2009/03/20
 * @since Barge 1.2.1 2009/03/20
 */
class HttpClient
{
	/**
	 * HTTP GET method. 
	 */
	const METHOD_GET = 'GET';
	
	/**
	 * HTTP POST method. 
	 */
	const METHOD_POST = 'POST';
	
	/**
	 * Dose not folow redirect. 
	 */
	const NO_REDIRECTS = 0;
	
	/**
	 * HTTP status
	 */
	const HTTP_OK = 200;
	const HTTP_URL_NOT_FOUND = 200;
	
	/**
	 * @var HashMap
	 */
	private $headers;
	
	/**
	 * @var int
	 */
	private $maxRedirects = 3;
	
	/**
	 * @var float
	 */
	private $timeout = null;
	
	/**
	 * @var float
	 */
	private $ignoreErrors = false;
	
	/**
	 * コンストラクタ
	 */
	public function __construct() 
	{
		$this->headers = new HashMap();
	}
	
	/**
	 * @param string $key
	 * @param mixed $value
	 */
	public function setHeader($key, $value)
	{
		$this->headers->put($key, $value);
	}
	
	/**
	 * @param string $key
	 * @param mixed $value
	 */
	public function setIgnoreErrors($ignore)
	{
		$this->ignoreErrors = $ignore;
	}
	
	/**
	 * @param int $redirect
	 */
	public function setMaxRedirects($redirect)
	{
		$this->maxRedirects = $redirect;
	}
	
	/**
	 * タイムアウト時間をミリ秒で指定する	
	 * @param int $millsecond ミリ秒を指定	
	 */
	public function setTimeout($millsecond)
	{
		$this->timeout = $millsecond;
	}
	
	/**
	 * @param string $url
	 * @return HttpResponse
	 */
	public function doGet($url)
	{
		$ctx = $this->createContext(self::METHOD_GET);
		$fp = @fopen($url, 'rb', false, $ctx);
		if ($fp === false) {         
			throw new HttpClientException("Problem with $url, $php_errormsg");      
		}
		return new HttpResponseImpl($fp);
	}
		
	/**
	 * @param string $url
	 * @param HashMap|string $data
	 * @return HttpResponse
	 */
	public function doPost($url, $data)
	{
		$ctx = $this->createContext(self::METHOD_POST);
		$fp = @fopen($url, 'rb', false, $ctx);
		if ($fp === false) {         
			throw new HttpClientException("Problem with $url, $php_errormsg");      
		}
		return new HttpResponseImpl($fp);
	}
	
	/**
	 * @param $method
	 * @return resource
	 */
	private function createContext($method)
	{
		$header = null;
		foreach ($this->headers as $key => $value) 
			$header.=$key.': '.urlencode($value)."\r\n";
			
		$params = array(
		  'http' => array(
			'method' => $method,
			'header' => $header,
			'proxy' => null,
			'max_redirects' => ($this->maxRedirects + 1),
//			'timeout' => ($this->timeout !== null ? ($this->timeout / 1000) : null),
			'ignore_errors' => $this->ignoreErrors
			));
		
		return stream_context_create($params);
	}
}

/**
 * @version 1.0 2009/02/21
 * @since Barge 1.2.1 2009/03/20
 */
interface HttpResponse
{
	/**
	 * @return string
	 * @since 1.0 2009/03/20
	 */
	public function getContents();
		
	/**
	 * @param int $len
	 * @return string
	 * @since 1.0 2009/03/20
	 */
	public function read($len = 1);
	
	/**
	 * @param int $len
	 * @param resource $conn
	 */
	public function readLine($len = null, $trim = true);

	/**
	 * @return int
	 * @since 1.0 2009/03/20
	 */
	public function getStatus();
	
	/**
	 * @since 1.0 2009/03/20
	 */
	public function release();
}

/**
 * @version 1.0 2009/03/20
 * @since Barge 1.2.1 2009/03/20 
 * @see HttpResponse
 */
class HttpResponseImpl implements HttpResponse
{
	/**
	 * @var resource
	 */
	private $conn;
	
	/**
	 * @param resource $conn
	 */
	public function __construct($conn)
	{
		$this->conn = $conn;
	}
	
	/**
	 * @param resource $conn
	 * @return string
	 */
	public function getContents()
	{
		$response = @stream_get_contents($this->conn);
		if ($response === false) {
			throw new HttpClientException("Problem reading data from $url, $php_errormsg");
		}      
		return $response;
	}
	
	/**
	 * @param int $len
	 * @return int
	 */
	public function read($len = 1)
	{
		if (feof($this->conn)) return null;
		$response = @stream_get_contents($this->conn, $len);
		if ($response === false) {
			throw new HttpClientException("Problem reading data from $url, $php_errormsg");
		}      
		return $response;
	}
	
	/**
	 * @param int $len
	 * @param bool $trim
	 * @return string
	 */
	public function readLine($len = null, $trim = true)
	{
		if (feof($this->conn)) return null;
		$response = @stream_get_line($this->conn, ($len !== null ? $len : -1), "\r\n");
		if ($response === false) {
			throw new HttpClientException("Problem reading data from $url, $php_errormsg");
		}      
		return rtrim($response, "\r\n");
	}
	
	/**
	 * Returns HTTP status code.
	 * @return int
	 */
	public function getStatus()
	{
		$metadata = @stream_get_meta_data($this->conn);
		$offset = 0;
		$status = -1;
		foreach ($metadata['wrapper_data'] as $header) {
			if ($offset++ == 0) {
				$boundary = explode(' ', $header);
				$status = intval($boundary[1]);
			}
		}
		return $status;
	}
	
	/**
	 * Release connection.
	 * @since 1.0 2009/03/20
	 */
	public function release()
	{
		flose($this->conn);
	}
}
